Skip to content

feat(release): reconcile the attested SBOM against the staged install - #310

Merged
mtskf merged 2 commits into
mainfrom
chore/verify-sbom-scope-reconcile-install
Jul 31, 2026
Merged

feat(release): reconcile the attested SBOM against the staged install#310
mtskf merged 2 commits into
mainfrom
chore/verify-sbom-scope-reconcile-install

Conversation

@mtskf

@mtskf mtskf commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

scripts/verify-sbom-scope.mjs previously checked only version syntax (exact semver), so a syft parsing regression or an altered SBOM carrying syntactically-valid-but-wrong versions (e.g. @codemirror/state@0.0.0) passed the release gate. This adds an opt-in --reconcile <stagingDir> that reconciles the attested SBOM's {name@version} set against an independent inventory read from the frozen prod staging tree's real package manifests (not the pnpm dir-name encoding — that is not a stable contract).

The strict check is off by default (the live-release path is byte-unchanged) and reaches CI only as a non-gating shadow step in publish.yml, so it collects pinned-syft (v1.42.3) vs manifest-derivation evidence on real staging trees before it can be promoted to a gate. That promotion is a tracked follow-up.

Changes

  • scripts/verify-sbom-scope.mjs:
    • deriveInstalledInventory(stagingDir) — fs adapter reading each installed package's real package.json from node_modules/.pnpm/*/node_modules/ (symlinked deps skipped; encoding-independent). Fail-closed: throws on unreadable .pnpm, a .pnpm entry with no manifest, or a manifest missing name/version.
    • reconcileSbomInventory({ sbom, installed, ignore }) — pure, set-based per-name version comparison (surplus→unexpected, deficit→missing, fully-disjoint→version-mismatch). ignore excludes the staging self-package by exact name@version, so a forged self entry at any other version is still flagged.
    • resolveReconcileArg(argv) — pure flag parser; missing value → exit 2.
    • main() wires the opt-in flag: syntax gate first, then reconcile; staging read failure → exit 2, violation → exit 1.
  • .github/workflows/publish.yml: non-gating (|| true) shadow reconcile step after the existing gating step; gating step untouched.
  • Tests: 32 new cases (pure reconcile incl. dual-major + self-package + forged-version + mixed-boundary; fs adapter fail-closed fixtures; end-to-end CLI exit 0/1/2).

Related

Test Plan

  • pnpm exec vitest run test/build/verify-sbom-scope.test.ts — 49 pass
  • pnpm compile + pnpm lint green on changed files
  • End-to-end against the real 23-package prod staging closure: exit 0 (matching SBOM incl. self-package), exit 1 (tampered valid-but-wrong version), exit 0 (default path, no flag)
  • On the next real release, inspect the shadow step's log for a clean reconcile before promoting it to a gate

mtskf added 2 commits August 1, 2026 08:18
verify-sbom-scope gains an opt-in --reconcile <stagingDir> that reads the
frozen prod staging tree's real package manifests (never the pnpm dir-name
encoding) and requires the attested SBOM's {name@version} set to match,
catching a syft parsing regression or an altered SBOM that carries
syntactically-valid-but-wrong versions past the syntax-only checks.

The flag is off by default (live-release path byte-unchanged); publish.yml
runs it as a NON-gating shadow step to collect pinned-syft-vs-derivation
evidence on real staging trees before it can be promoted to a gate. The
staging self-package is excluded by exact name@version, so a forged self
entry is still flagged. Fail-closed throughout (exit 2 on any staging read
failure or malformed manifest; empty inventory can only fail, never pass).
reconcileSbomInventory filters SBOM packages through npmPackages() so a
no-purl SPDX document-root or a non-npm (pypi) entry is not misclassified
as 'unexpected'. No reconcile-side test pinned that filter — dropping it
kept every test green. Add a reconcile case feeding a no-purl root + a pypi
entry and asserting a clean reconcile, so removing the discriminator fails.
@mtskf
mtskf merged commit fdf7585 into main Jul 31, 2026
1 check passed
@mtskf
mtskf deleted the chore/verify-sbom-scope-reconcile-install branch July 31, 2026 22:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant